home *** CD-ROM | disk | FTP | other *** search
/ Komputer for Alle 2004 #2 / K-CD-2-2004.ISO / OpenOffice Sv / f_0397 / python-core-2.2.2 / lib / formatter.py < prev    next >
Encoding:
Python Source  |  2003-07-18  |  15.0 KB  |  455 lines

  1. """Generic output formatting.
  2.  
  3. Formatter objects transform an abstract flow of formatting events into
  4. specific output events on writer objects. Formatters manage several stack
  5. structures to allow various properties of a writer object to be changed and
  6. restored; writers need not be able to handle relative changes nor any sort
  7. of ``change back'' operation. Specific writer properties which may be
  8. controlled via formatter objects are horizontal alignment, font, and left
  9. margin indentations. A mechanism is provided which supports providing
  10. arbitrary, non-exclusive style settings to a writer as well. Additional
  11. interfaces facilitate formatting events which are not reversible, such as
  12. paragraph separation.
  13.  
  14. Writer objects encapsulate device interfaces. Abstract devices, such as
  15. file formats, are supported as well as physical devices. The provided
  16. implementations all work with abstract devices. The interface makes
  17. available mechanisms for setting the properties which formatter objects
  18. manage and inserting data into the output.
  19. """
  20.  
  21. import string
  22. import sys
  23. from types import StringType
  24.  
  25.  
  26. AS_IS = None
  27.  
  28.  
  29. class NullFormatter:
  30.     """A formatter which does nothing.
  31.  
  32.     If the writer parameter is omitted, a NullWriter instance is created.
  33.     No methods of the writer are called by NullFormatter instances.
  34.  
  35.     Implementations should inherit from this class if implementing a writer
  36.     interface but don't need to inherit any implementation.
  37.  
  38.     """
  39.  
  40.     def __init__(self, writer=None):
  41.         if not writer:
  42.             writer = NullWriter()
  43.         self.writer = writer
  44.     def end_paragraph(self, blankline): pass
  45.     def add_line_break(self): pass
  46.     def add_hor_rule(self, *args, **kw): pass
  47.     def add_label_data(self, format, counter, blankline=None): pass
  48.     def add_flowing_data(self, data): pass
  49.     def add_literal_data(self, data): pass
  50.     def flush_softspace(self): pass
  51.     def push_alignment(self, align): pass
  52.     def pop_alignment(self): pass
  53.     def push_font(self, x): pass
  54.     def pop_font(self): pass
  55.     def push_margin(self, margin): pass
  56.     def pop_margin(self): pass
  57.     def set_spacing(self, spacing): pass
  58.     def push_style(self, *styles): pass
  59.     def pop_style(self, n=1): pass
  60.     def assert_line_data(self, flag=1): pass
  61.  
  62.  
  63. class AbstractFormatter:
  64.     """The standard formatter.
  65.  
  66.     This implementation has demonstrated wide applicability to many writers,
  67.     and may be used directly in most circumstances.  It has been used to
  68.     implement a full-featured World Wide Web browser.
  69.  
  70.     """
  71.  
  72.     #  Space handling policy:  blank spaces at the boundary between elements
  73.     #  are handled by the outermost context.  "Literal" data is not checked
  74.     #  to determine context, so spaces in literal data are handled directly
  75.     #  in all circumstances.
  76.  
  77.     def __init__(self, writer):
  78.         self.writer = writer            # Output device
  79.         self.align = None               # Current alignment
  80.         self.align_stack = []           # Alignment stack
  81.         self.font_stack = []            # Font state
  82.         self.margin_stack = []          # Margin state
  83.         self.spacing = None             # Vertical spacing state
  84.         self.style_stack = []           # Other state, e.g. color
  85.         self.nospace = 1                # Should leading space be suppressed
  86.         self.softspace = 0              # Should a space be inserted
  87.         self.para_end = 1               # Just ended a paragraph
  88.         self.parskip = 0                # Skipped space between paragraphs?
  89.         self.hard_break = 1             # Have a hard break
  90.         self.have_label = 0
  91.  
  92.     def end_paragraph(self, blankline):
  93.         if not self.hard_break:
  94.             self.writer.send_line_break()
  95.             self.have_label = 0
  96.         if self.parskip < blankline and not self.have_label:
  97.             self.writer.send_paragraph(blankline - self.parskip)
  98.             self.parskip = blankline
  99.             self.have_label = 0
  100.         self.hard_break = self.nospace = self.para_end = 1
  101.         self.softspace = 0
  102.  
  103.     def add_line_break(self):
  104.         if not (self.hard_break or self.para_end):
  105.             self.writer.send_line_break()
  106.             self.have_label = self.parskip = 0
  107.         self.hard_break = self.nospace = 1
  108.         self.softspace = 0
  109.  
  110.     def add_hor_rule(self, *args, **kw):
  111.         if not self.hard_break:
  112.             self.writer.send_line_break()
  113.         apply(self.writer.send_hor_rule, args, kw)
  114.         self.hard_break = self.nospace = 1
  115.         self.have_label = self.para_end = self.softspace = self.parskip = 0
  116.  
  117.     def add_label_data(self, format, counter, blankline = None):
  118.         if self.have_label or not self.hard_break:
  119.             self.writer.send_line_break()
  120.         if not self.para_end:
  121.             self.writer.send_paragraph((blankline and 1) or 0)
  122.         if type(format) is StringType:
  123.             self.writer.send_label_data(self.format_counter(format, counter))
  124.         else:
  125.             self.writer.send_label_data(format)
  126.         self.nospace = self.have_label = self.hard_break = self.para_end = 1
  127.         self.softspace = self.parskip = 0
  128.  
  129.     def format_counter(self, format, counter):
  130.         label = ''
  131.         for c in format:
  132.             if c == '1':
  133.                 label = label + ('%d' % counter)
  134.             elif c in 'aA':
  135.                 if counter > 0:
  136.                     label = label + self.format_letter(c, counter)
  137.             elif c in 'iI':
  138.                 if counter > 0:
  139.                     label = label + self.format_roman(c, counter)
  140.             else:
  141.                 label = label + c
  142.         return label
  143.  
  144.     def format_letter(self, case, counter):
  145.         label = ''
  146.         while counter > 0:
  147.             counter, x = divmod(counter-1, 26)
  148.             # This makes a strong assumption that lowercase letters
  149.             # and uppercase letters form two contiguous blocks, with
  150.             # letters in order!
  151.             s = chr(ord(case) + x)
  152.             label = s + label
  153.         return label
  154.  
  155.     def format_roman(self, case, counter):
  156.         ones = ['i', 'x', 'c', 'm']
  157.         fives = ['v', 'l', 'd']
  158.         label, index = '', 0
  159.         # This will die of IndexError when counter is too big
  160.         while counter > 0:
  161.             counter, x = divmod(counter, 10)
  162.             if x == 9:
  163.                 label = ones[index] + ones[index+1] + label
  164.             elif x == 4:
  165.                 label = ones[index] + fives[index] + label
  166.             else:
  167.                 if x >= 5:
  168.                     s = fives[index]
  169.                     x = x-5
  170.                 else:
  171.                     s = ''
  172.                 s = s + ones[index]*x
  173.                 label = s + label
  174.             index = index + 1
  175.         if case == 'I':
  176.             return label.upper()
  177.         return label
  178.  
  179.     def add_flowing_data(self, data,
  180.                          # These are only here to load them into locals:
  181.                          whitespace = string.whitespace,
  182.                          join = string.join, split = string.split):
  183.         if not data: return
  184.         # The following looks a bit convoluted but is a great improvement over
  185.         # data = regsub.gsub('[' + string.whitespace + ']+', ' ', data)
  186.         prespace = data[:1] in whitespace
  187.         postspace = data[-1:] in whitespace
  188.         data = join(split(data))
  189.         if self.nospace and not data:
  190.             return
  191.         elif prespace or self.softspace:
  192.             if not data:
  193.                 if not self.nospace:
  194.                     self.softspace = 1
  195.                     self.parskip = 0
  196.                 return
  197.             if not self.nospace:
  198.                 data = ' ' + data
  199.         self.hard_break = self.nospace = self.para_end = \
  200.                           self.parskip = self.have_label = 0
  201.         self.softspace = postspace
  202.         self.writer.send_flowing_data(data)
  203.  
  204.     def add_literal_data(self, data):
  205.         if not data: return
  206.         if self.softspace:
  207.             self.writer.send_flowing_data(" ")
  208.         self.hard_break = data[-1:] == '\n'
  209.         self.nospace = self.para_end = self.softspace = \
  210.                        self.parskip = self.have_label = 0
  211.         self.writer.send_literal_data(data)
  212.  
  213.     def flush_softspace(self):
  214.         if self.softspace:
  215.             self.hard_break = self.para_end = self.parskip = \
  216.                               self.have_label = self.softspace = 0
  217.             self.nospace = 1
  218.             self.writer.send_flowing_data(' ')
  219.  
  220.     def push_alignment(self, align):
  221.         if align and align != self.align:
  222.             self.writer.new_alignment(align)
  223.             self.align = align
  224.             self.align_stack.append(align)
  225.         else:
  226.             self.align_stack.append(self.align)
  227.  
  228.     def pop_alignment(self):
  229.         if self.align_stack:
  230.             del self.align_stack[-1]
  231.         if self.align_stack:
  232.             self.align = align = self.align_stack[-1]
  233.             self.writer.new_alignment(align)
  234.         else:
  235.             self.align = None
  236.             self.writer.new_alignment(None)
  237.  
  238.     def push_font(self, (size, i, b, tt)):
  239.         if self.softspace:
  240.             self.hard_break = self.para_end = self.softspace = 0
  241.             self.nospace = 1
  242.             self.writer.send_flowing_data(' ')
  243.         if self.font_stack:
  244.             csize, ci, cb, ctt = self.font_stack[-1]
  245.             if size is AS_IS: size = csize
  246.             if i is AS_IS: i = ci
  247.             if b is AS_IS: b = cb
  248.             if tt is AS_IS: tt = ctt
  249.         font = (size, i, b, tt)
  250.         self.font_stack.append(font)
  251.         self.writer.new_font(font)
  252.  
  253.     def pop_font(self):
  254.         if self.font_stack:
  255.             del self.font_stack[-1]
  256.         if self.font_stack:
  257.             font = self.font_stack[-1]
  258.         else:
  259.             font = None
  260.         self.writer.new_font(font)
  261.  
  262.     def push_margin(self, margin):
  263.         self.margin_stack.append(margin)
  264.         fstack = filter(None, self.margin_stack)
  265.         if not margin and fstack:
  266.             margin = fstack[-1]
  267.         self.writer.new_margin(margin, len(fstack))
  268.  
  269.     def pop_margin(self):
  270.         if self.margin_stack:
  271.             del self.margin_stack[-1]
  272.         fstack = filter(None, self.margin_stack)
  273.         if fstack:
  274.             margin = fstack[-1]
  275.         else:
  276.             margin = None
  277.         self.writer.new_margin(margin, len(fstack))
  278.  
  279.     def set_spacing(self, spacing):
  280.         self.spacing = spacing
  281.         self.writer.new_spacing(spacing)
  282.  
  283.     def push_style(self, *styles):
  284.         if self.softspace:
  285.             self.hard_break = self.para_end = self.softspace = 0
  286.             self.nospace = 1
  287.             self.writer.send_flowing_data(' ')
  288.         for style in styles:
  289.             self.style_stack.append(style)
  290.         self.writer.new_styles(tuple(self.style_stack))
  291.  
  292.     def pop_style(self, n=1):
  293.         del self.style_stack[-n:]
  294.         self.writer.new_styles(tuple(self.style_stack))
  295.  
  296.     def assert_line_data(self, flag=1):
  297.         self.nospace = self.hard_break = not flag
  298.         self.para_end = self.parskip = self.have_label = 0
  299.  
  300.  
  301. class NullWriter:
  302.     """Minimal writer interface to use in testing & inheritance.
  303.  
  304.     A writer which only provides the interface definition; no actions are
  305.     taken on any methods.  This should be the base class for all writers
  306.     which do not need to inherit any implementation methods.
  307.  
  308.     """
  309.     def __init__(self): pass
  310.     def flush(self): pass
  311.     def new_alignment(self, align): pass
  312.     def new_font(self, font): pass
  313.     def new_margin(self, margin, level): pass
  314.     def new_spacing(self, spacing): pass
  315.     def new_styles(self, styles): pass
  316.     def send_paragraph(self, blankline): pass
  317.     def send_line_break(self): pass
  318.     def send_hor_rule(self, *args, **kw): pass
  319.     def send_label_data(self, data): pass
  320.     def send_flowing_data(self, data): pass
  321.     def send_literal_data(self, data): pass
  322.  
  323.  
  324. class AbstractWriter(NullWriter):
  325.     """A writer which can be used in debugging formatters, but not much else.
  326.  
  327.     Each method simply announces itself by printing its name and
  328.     arguments on standard output.
  329.  
  330.     """
  331.  
  332.     def new_alignment(self, align):
  333.         print "new_alignment(%s)" % `align`
  334.  
  335.     def new_font(self, font):
  336.         print "new_font(%s)" % `font`
  337.  
  338.     def new_margin(self, margin, level):
  339.         print "new_margin(%s, %d)" % (`margin`, level)
  340.  
  341.     def new_spacing(self, spacing):
  342.         print "new_spacing(%s)" % `spacing`
  343.  
  344.     def new_styles(self, styles):
  345.         print "new_styles(%s)" % `styles`
  346.  
  347.     def send_paragraph(self, blankline):
  348.         print "send_paragraph(%s)" % `blankline`
  349.  
  350.     def send_line_break(self):
  351.         print "send_line_break()"
  352.  
  353.     def send_hor_rule(self, *args, **kw):
  354.         print "send_hor_rule()"
  355.  
  356.     def send_label_data(self, data):
  357.         print "send_label_data(%s)" % `data`
  358.  
  359.     def send_flowing_data(self, data):
  360.         print "send_flowing_data(%s)" % `data`
  361.  
  362.     def send_literal_data(self, data):
  363.         print "send_literal_data(%s)" % `data`
  364.  
  365.  
  366. class DumbWriter(NullWriter):
  367.     """Simple writer class which writes output on the file object passed in
  368.     as the file parameter or, if file is omitted, on standard output.  The
  369.     output is simply word-wrapped to the number of columns specified by
  370.     the maxcol parameter.  This class is suitable for reflowing a sequence
  371.     of paragraphs.
  372.  
  373.     """
  374.  
  375.     def __init__(self, file=None, maxcol=72):
  376.         self.file = file or sys.stdout
  377.         self.maxcol = maxcol
  378.         NullWriter.__init__(self)
  379.         self.reset()
  380.  
  381.     def reset(self):
  382.         self.col = 0
  383.         self.atbreak = 0
  384.  
  385.     def send_paragraph(self, blankline):
  386.         self.file.write('\n'*blankline)
  387.         self.col = 0
  388.         self.atbreak = 0
  389.  
  390.     def send_line_break(self):
  391.         self.file.write('\n')
  392.         self.col = 0
  393.         self.atbreak = 0
  394.  
  395.     def send_hor_rule(self, *args, **kw):
  396.         self.file.write('\n')
  397.         self.file.write('-'*self.maxcol)
  398.         self.file.write('\n')
  399.         self.col = 0
  400.         self.atbreak = 0
  401.  
  402.     def send_literal_data(self, data):
  403.         self.file.write(data)
  404.         i = data.rfind('\n')
  405.         if i >= 0:
  406.             self.col = 0
  407.             data = data[i+1:]
  408.         data = data.expandtabs()
  409.         self.col = self.col + len(data)
  410.         self.atbreak = 0
  411.  
  412.     def send_flowing_data(self, data):
  413.         if not data: return
  414.         atbreak = self.atbreak or data[0] in string.whitespace
  415.         col = self.col
  416.         maxcol = self.maxcol
  417.         write = self.file.write
  418.         for word in data.split():
  419.             if atbreak:
  420.                 if col + len(word) >= maxcol:
  421.                     write('\n')
  422.                     col = 0
  423.                 else:
  424.                     write(' ')
  425.                     col = col + 1
  426.             write(word)
  427.             col = col + len(word)
  428.             atbreak = 1
  429.         self.col = col
  430.         self.atbreak = data[-1] in string.whitespace
  431.  
  432.  
  433. def test(file = None):
  434.     w = DumbWriter()
  435.     f = AbstractFormatter(w)
  436.     if file:
  437.         fp = open(file)
  438.     elif sys.argv[1:]:
  439.         fp = open(sys.argv[1])
  440.     else:
  441.         fp = sys.stdin
  442.     while 1:
  443.         line = fp.readline()
  444.         if not line:
  445.             break
  446.         if line == '\n':
  447.             f.end_paragraph(1)
  448.         else:
  449.             f.add_flowing_data(line)
  450.     f.end_paragraph(0)
  451.  
  452.  
  453. if __name__ == '__main__':
  454.     test()
  455.